Ch5. 클로저
클로저란?
클로저란 함수가 선언될 당시의 렉시컬 스코프를 기억하며, 함수가 그 스코프 밖에서 실행될 때에도 해당 스코프에 접근할 수 있게 하는 기능이다.
5.1 깨달음
function foo() {
var a = 2;
function bar() {
console.log(a);
}
bar();
}
foo();
bar()는 단순히 스코프 규칙에 따라a를 참조- 클로저 일부이지만 명확히 드러나진 않음
5.2 클로저 예시
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2
bar()는foo()의 스코프를 기억하고 있음foo()의 실행이 끝난 후에도bar()는 여전히a에 접근 가능 → 클로저
5.3 클로저의 작동 방식
var fn;
function foo() {
var a = 2;
function baz() {
console.log(a);
}
fn = baz;
}
function bar() {
fn(); // 2
}
foo();
bar();
baz는foo의 렉시컬 스코프에 대한 클로저를 가지므로 여전히a를 참조 가능
setTimeout 클로저
function wait(message) {
setTimeout(() => {
console.log(message);
}, 1000);
}
wait("Hello, Closure!");
setTimeout콜백도message에 접근 가능 → 클로저
jQuery 예제
function setupBot(name, selector) {
$(selector).click(function activator() {
console.log("Activating: " + name);
});
}
setupBot("closure bot 1", "#bot_1");
setupBot("closure bot 2", "#bot_2");
- 이벤트 핸들러 내부에서도
setupBot의 변수name을 참조함 → 클로저
IIFE 예제
function foo() {
var a = 2;
(function IIFE() {
console.log(a);
})();
}
- 이 경우는 스코프 안에서 호출 → 명확한 클로저는 아님
5.4 반복문과 클로저
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
// 출력: 6, 6, 6, 6, 6
해결책 1: 새 스코프 생성
for (var i = 1; i <= 5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, i * 1000);
})();
}
해결책 2: 인자 전달
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
해결책 3: let 사용
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
→ let은 반복 시마다 새로운 스코프를 생성함
5.5 모듈
모듈 패턴
function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join(", "));
}
return {
doSomething,
doAnother,
};
}
var coolModule = CoolModule();
coolModule.doSomething();
coolModule.doAnother();
- 클로저를 활용한 데이터 은닉, API 공개
싱글톤 모듈 (IIFE)
var foo = (function CoolModule() {
// ...
return {
doSomething,
doAnother,
};
})();
모듈 로더 예시
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get(name) {
return modules[name];
}
return {
define,
get,
};
})();
5.5.2 ES6 모듈
- 파일 단위 모듈
import,export사용- 정적으로 분석 가능, 런타임 의존성 없음
// bar.js
export function hello(who) {
return "Let me introduce: " + who;
}
// foo.js
import { hello } from "./bar.js";
const hungry = "hippo";
export function awesome() {
console.log(hello(hungry).toUpperCase());
}
5.6 정리
- 클로저는 함수가 선언된 렉시컬 스코프를 기억하는 기능
- 반복문, 타이머, 이벤트 핸들러에서도 활발히 사용됨
- 모듈은 클로저를 활용한 구조이며, ES6 이후에는 파일 기반 정적 모듈 시스템이 도입됨